package com.jwater.console.controller;

import com.google.common.collect.Lists;
import com.jwater.console.model.*;
import com.jwater.console.service.ConsoleService;
import com.jwater.core.common.S;
import com.jwater.core.manager.common.model.*;
import com.jwater.launch.HomePath;
import jwater.com.alibaba.fastjson.JSON;
import jwater.com.alibaba.fastjson.JSONArray;
import jwater.org.apache.commons.io.IOUtils;
import jwater.org.springframework.util.StringUtils;
import org.apache.commons.io.FilenameUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.Map.Entry;
import java.util.stream.Collectors;

/**
 * @description: 控制台所有接口
 * @author: jnner
 * @createDate: 2021/2/5
 * @version: 1.0
 */
@RestController
@RequestMapping(path = "/console")
public class ConsoleController {
    protected static Logger logger = LoggerFactory.getLogger(ConsoleController.class);

    @Autowired
    private ConsoleService consoleService;

    /**
     * 登录请求，返回登录成功后的token，token需要作过期校验
     *
     * @param datas
     * @return
     */
    @RequestMapping(path = "/login", method = RequestMethod.POST)
    public Result login(@RequestBody Map<String, String> datas) {
        try {
            String regionName = datas.get("regionName");
            String username = datas.get("username");
            String password = datas.get("password");
            // 可以采用jwt生成token
            String token = UUID.randomUUID().toString().replace("-", "").concat(".").concat(regionName).concat(".")
                    .concat(username);
            if (consoleService.login(regionName, username, password, token)) {
                Result result = Result.success();
                result.setToken(token);
                return result;
            }
            return Result.error("用户名或者密码错误！");
        } catch (Exception e) {
            logger.error(e.toString(), e);
            return Result.error(e.toString());
        }
    }

    /**
     * 退出，移除token
     *
     * @param request
     * @return
     */
    @RequestMapping(path = "/logout", method = RequestMethod.POST)
    public Result logout(HttpServletRequest request) {
        try {
            String token = request.getHeader("token");
            consoleService.logout(token);
            return Result.success();
        } catch (Exception e) {
            logger.error(e.toString(), e);
            return Result.error(e.toString());
        }
    }

    /**
     * 获取jwater根目录下conf目录下的配置列表
     *
     * @return
     */
    @RequestMapping(path = "/getConfList", method = RequestMethod.GET)
    public Result getConfList() {
        try {
            return Result.success(consoleService.getConfList());
        } catch (Exception e) {
            logger.error(e.toString(), e);
            return Result.error(e.toString());
        }
    }

    /**
     * 获取指定路径下的配置内容
     *
     * @param datas
     * @return
     */
    @RequestMapping(path = "/getConfContent", method = RequestMethod.POST)
    public Result getConfContent(@RequestBody Map<String, String> datas) {
        try {
            String path = datas.get("path");
            return Result.success(consoleService.getConfContent(path));
        } catch (Exception e) {
            logger.error(e.toString(), e);
            return Result.error(e.toString());
        }
    }

    /**
     * 将文件内容保存到指定路径下
     *
     * @param datas
     * @return
     */
    @RequestMapping(path = "/saveConfContent", method = RequestMethod.POST)
    public Result saveConfContent(@RequestBody Map<String, String> datas) {
        try {
            String path = datas.get("path");
            String content = datas.get("content");
            consoleService.saveConfContent(path, content);
            return Result.success();
        } catch (Exception e) {
            logger.error(e.toString(), e);
            return Result.error(e.toString());
        }
    }

    /**
     * 新建一个配置文件，可以建在jwater根目录的conf目录下，也可以建在region/deploy的目录下
     *
     * @param datas
     * @return
     */
    @RequestMapping(path = "/newConf", method = RequestMethod.POST)
    public Result newConf(@RequestBody Map<String, String> datas) {
        try {
            boolean sys = Boolean.valueOf(datas.get("sys"));
            String regionName = datas.get("regionName");
            String deployName = datas.get("deployName");
            String fileName = datas.get("fileName");
            consoleService.newConf(sys, regionName, deployName, fileName);
            return Result.success();
        } catch (Exception e) {
            logger.error(e.toString(), e);
            return Result.error(e.toString());
        }
    }

    /**
     * 删除指定路径的配置文件
     *
     * @param datas
     * @return
     */
    @RequestMapping(path = "/deleteConf", method = RequestMethod.POST)
    public Result deleteConf(@RequestBody Map<String, String> datas) {
        try {
            String path = datas.get("path");
            consoleService.deleteConf(path);
            return Result.success();
        } catch (Exception e) {
            logger.error(e.toString(), e);
            return Result.error(e.toString());
        }
    }

    /**
     * 获取管理进程的列表
     *
     * @return
     */
    @RequestMapping(path = "/getMasterList", method = RequestMethod.GET)
    public Result getMasterList() {
        try {
            List<MasterInfo> masters = consoleService.getMasterList();
            masters.stream().sorted(Comparator.comparing(MasterInfo::getIndex));
            return Result.success(masters);
        } catch (Exception e) {
            logger.error(e.toString(), e);
            return Result.error(e.toString());
        }
    }

    /**
     * 重启控制台进程
     *
     * @return
     */
    @RequestMapping(path = "/rebootConsole", method = RequestMethod.POST)
    public Result rebootConsole() {
        try {
            consoleService.rebootConsole();
            return Result.success();
        } catch (Exception e) {
            logger.error(e.toString(), e);
            return Result.error(e.toString());
        }
    }

    /**
     * 启动管理进程
     *
     * @param datas
     * @return
     */
    @RequestMapping(path = "/startMaster", method = RequestMethod.POST)
    public Result startMaster(@RequestBody Map<String, String> datas) {
        try {
            int index = Integer.parseInt(datas.get("index"));
            consoleService.startMaster(index);
            return Result.success();
        } catch (Exception e) {
            logger.error(e.toString(), e);
            return Result.error(e.toString());
        }
    }

    /**
     * 停止管理进程
     *
     * @param datas
     * @return
     */
    @RequestMapping(path = "/stopMaster", method = RequestMethod.POST)
    public Result stopMaster(@RequestBody Map<String, String> datas) {
        try {
            int index = Integer.parseInt(datas.get("index"));
            consoleService.stopMaster(index);
            return Result.success();
        } catch (Exception e) {
            logger.error(e.toString(), e);
            return Result.error(e.toString());
        }
    }

    /**
     * 获取主机列表
     *
     * @return
     */
    @RequestMapping(path = "/getHostList", method = RequestMethod.GET)
    public Result getHostList() {
        try {
            List<HostInfo> hosts = consoleService.getHostList().stream()
                    .sorted(Comparator.comparing(HostInfo::getHost)).collect(Collectors.toList());
            return Result.success(hosts);
        } catch (Exception e) {
            logger.error(e.toString(), e);
            return Result.error(e.toString());
        }
    }

    /**
     * 启动代理进程
     *
     * @param host
     * @return
     */
    @RequestMapping(path = "/startProxy", method = RequestMethod.GET)
    public Result startProxy(String host) {
        try {
            consoleService.startProxy(host);
            return Result.success();
        } catch (Exception e) {
            logger.error(e.toString(), e);
            return Result.error(e.toString());
        }
    }

    /**
     * 停止代理进程
     *
     * @param host
     * @return
     */
    @RequestMapping(path = "/stopProxy", method = RequestMethod.GET)
    public Result stopProxy(String host) {
        try {
            consoleService.stopProxy(host);
            return Result.success();
        } catch (Exception e) {
            logger.error(e.toString(), e);
            return Result.error(e.toString());
        }
    }

    /**
     * 获取菜单树
     *
     * @param regionName
     * @return
     */
    @RequestMapping(path = "/getMenuTree", method = RequestMethod.GET)
    public Result getMenuTree(@RequestParam("regionName") String regionName) {
        try {
            List<Menu> menus = getMenus(regionName);
            List<TreeNode> treeNodes = menuTreeNode(0, menus, null);
            return Result.success(treeNodes);
        } catch (Exception e) {
            logger.error(e.toString(), e);
            return Result.error(e.toString());
        }
    }

    /**
     * 获取菜单列表信息
     *
     * @param regionName
     * @return
     */
    @RequestMapping(path = "/getMenuList", method = RequestMethod.GET)
    public Result getMenuList(@RequestParam("regionName") String regionName) {
        try {
            List<Menu> menus = getMenus(regionName);
            return Result.success(menus);
        } catch (Exception e) {
            logger.error(e.toString(), e);
            return Result.error(e.toString());
        }
    }

    /**
     * 获取指定的菜单
     *
     * @param regionName
     * @param menuId
     * @return
     */
    @RequestMapping(path = "/getMenuById", method = RequestMethod.GET)
    public Result getMenuById(@RequestParam("regionName") String regionName, @RequestParam("menuId") int menuId) {
        try {
            List<Menu> menus = getMenus(regionName);
            return Result.success(findMenu(menus, menuId));
        } catch (Exception e) {
            logger.error(e.toString(), e);
            return Result.error(e.toString());
        }
    }

    /**
     * 添加菜单
     *
     * @param datas
     * @return
     */
    @RequestMapping(path = "/addMenu", method = RequestMethod.POST)
    public Result addMenu(@RequestBody Map<String, String> datas) {
        try {
            String regionName = datas.get("regionName");
            String menuName = datas.get("menuName");
            String parentId = datas.get("parentId");
            int orderNum = Integer.parseInt(datas.get("orderNum"));
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            String createTime = sdf.format(new Date());
            List<Menu> menus = getMenus(regionName);
            int menuId = generateMenuId(menus);
            Menu menu = new Menu();
            menu.setMenuId(menuId);
            menu.setMenuName(menuName);
            menu.setParentId(Integer.parseInt(parentId));
            menu.setOrderNum(orderNum);
            menu.setCreateTime(createTime);
            menus.add(menu);
            consoleService.saveMenu(regionName, JSON.toJSONString(menus, true));
            return Result.success();
        } catch (Exception e) {
            logger.error(e.toString(), e);
            return Result.error(e.toString());
        }
    }

    /**
     * 更新菜单
     *
     * @param datas
     * @return
     */
    @RequestMapping(path = "/updateMenu", method = RequestMethod.POST)
    public Result updateMenu(@RequestBody Map<String, String> datas) {
        try {
            String regionName = datas.get("regionName");
            String menuId = datas.get("menuId");
            String menuName = datas.get("menuName");
            String parentId = datas.get("parentId");
            int orderNum = Integer.parseInt(datas.get("orderNum"));
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            String createTime = sdf.format(new Date());
            List<Menu> menus = getMenus(regionName);
            Menu menu = findMenu(menus, Integer.parseInt(menuId));
            menu.setMenuName(menuName);
            menu.setParentId(Integer.parseInt(parentId));
            menu.setOrderNum(orderNum);
            menu.setCreateTime(createTime);
            consoleService.saveMenu(regionName, JSON.toJSONString(menus, true));
            return Result.success();
        } catch (Exception e) {
            logger.error(e.toString(), e);
            return Result.error(e.toString());
        }
    }

    /**
     * 删除菜单
     *
     * @param regionName
     * @param menuId
     * @return
     */
    @RequestMapping(path = "/deleteMenu", method = RequestMethod.GET)
    public Result deleteMenu(@RequestParam("regionName") String regionName, @RequestParam("menuId") int menuId) {
        try {
            List<Menu> menus = getMenus(regionName);
            Menu child = findMenuByParentId(menus, menuId);
            if (child != null) {
                return Result.error("删除失败，请先删除子菜单");
            }
            menus = excludeMenu(menus, menuId);
            consoleService.saveMenu(regionName, JSON.toJSONString(menus, true));
            return Result.success();
        } catch (Exception e) {
            logger.error(e.toString(), e);
            return Result.error(e.toString());
        }
    }

    /**
     * 生成菜单编号，采用自增方式
     *
     * @param menus
     * @return
     */
    private int generateMenuId(List<Menu> menus) {
        Menu other = new Menu();
        other.setMenuId(1);
        return menus.stream().collect(Collectors.maxBy((o1, o2) -> {
            return o1.getMenuId() - o2.getMenuId();
        })).orElse(other).getMenuId() + 1;
    }

    /**
     * 过滤掉指定的菜单
     *
     * @param menus
     * @param menuId
     * @return
     */
    private List<Menu> excludeMenu(List<Menu> menus, int menuId) {
        return menus.stream().filter(m -> m.getMenuId() != menuId).collect(Collectors.toList());
    }

    /**
     * 查找指定的菜单
     *
     * @param menus
     * @param menuId
     * @return
     */
    private Menu findMenu(List<Menu> menus, int menuId) {
        return menus.stream().filter(m -> m.getMenuId() == menuId).findFirst().get();
    }

    /**
     * 查找父节点下任意一个子菜单
     *
     * @param menus
     * @param menuId
     * @return
     */
    private Menu findMenuByParentId(List<Menu> menus, int menuId) {
        return menus.stream().filter(m -> m.getParentId() == menuId).findFirst().orElse(null);
    }

    /**
     * 获取所有菜单
     *
     * @param regionName
     * @return
     * @throws Exception
     */
    private List<Menu> getMenus(String regionName) throws Exception {
        String content = consoleService.getMenuList(regionName);
        List<Menu> menus = new ArrayList<Menu>();
        if (!StringUtils.isEmpty(content)) {
            JSONArray array = JSON.parseArray(content);
            for (int i = 0; i < array.size(); i++) {
                Menu menu = JSON.toJavaObject(array.getJSONObject(i), Menu.class);
                menus.add(menu);
            }
        }
        menus = menus.stream().sorted((m1, m2) -> {
            return m1.getOrderNum() - m2.getOrderNum();
        }).collect(Collectors.toList());
        return menus;
    }

    /**
     * 获取父节点下的所有菜单
     *
     * @param menus
     * @param parentId
     * @return
     */
    private List<Menu> getChildren(List<Menu> menus, int parentId) {
        return menus.stream().filter(m -> m.getParentId() == parentId).collect(Collectors.toList());
    }

    /**
     * 生成菜单树
     *
     * @param id
     * @param all
     * @param parent
     * @return
     */
    private List<TreeNode> menuTreeNode(int id, List<Menu> all, TreeNode parent) {
        List<TreeNode> treeNodes = new ArrayList<TreeNode>();
        List<Menu> menus = getChildren(all, id);
        for (Menu menu : menus) {
            TreeNode treeNode = new TreeNode();
            if (parent == null) {
                treeNode.setPath("/" + menu.getMenuName());
            } else {
                treeNode.setPath(parent.getPath() + "/" + menu.getMenuName());
            }
            treeNode.setId(menu.getMenuId());
            treeNode.setLabel(menu.getMenuName());
            treeNode.setChildren(menuTreeNode(menu.getMenuId(), all, treeNode));
            treeNodes.add(treeNode);
        }
        return treeNodes;
    }

    /**
     * 获取安装包列表
     *
     * @param regionName
     * @return
     */
    @RequestMapping(path = "/getDeployList", method = RequestMethod.GET)
    public Result getDeployList(String regionName) {
        try {
            return Result.success(consoleService.getDeployList(regionName));
        } catch (Exception e) {
            logger.error(e.toString(), e);
            return Result.error(e.toString());
        }
    }

    /**
     * 上传安装包
     *
     * @param path
     * @param regionName
     * @param deployName
     * @param keepConf
     * @param file
     * @return
     */
    @RequestMapping(path = "/upload", method = RequestMethod.POST)
    public Result upload(String path, String regionName, String deployName, String keepConf,
                         @RequestParam("file") MultipartFile file) {
        try {
            String fileName = file.getOriginalFilename();
            logger.info("upload fileName:" + fileName);
            File tmpFile = new File(HomePath.getTmpFilePath(), UUID.randomUUID().toString());
            if (!tmpFile.getParentFile().exists()) {
                tmpFile.getParentFile().mkdirs();
            }
            file.transferTo(tmpFile);
            consoleService.deploy(path, regionName, deployName, keepConf, fileName, tmpFile);
            return Result.success();
        } catch (Exception e) {
            logger.error(e.toString(), e);
            return Result.error(e.toString());
        }
    }

    /**
     * 移除安装包
     *
     * @param regionName
     * @param deployName
     * @return
     */
    @RequestMapping(path = "/removeDeploy", method = RequestMethod.GET)
    public Result removeDeploy(String regionName, String deployName) {
        try {
            consoleService.removeDeploy(regionName, deployName);
            return Result.success();
        } catch (Exception e) {
            logger.error(e.toString(), e);
            return Result.error(e.toString());
        }
    }

    /**
     * 获取工作进程列表
     *
     * @param regionName
     * @return
     */
    @RequestMapping(path = "/getWorkerList", method = RequestMethod.GET)
    public Result getWorkerList(String regionName) {
        try {
            if (!consoleService.checkConnect()) {
                return Result.error("请先启动代理进程和管理进程");
            }
            return Result.success(deployWorkers(consoleService.getWorkerList(regionName)));
        } catch (Exception e) {
            logger.error(e.toString(), e);
            return Result.error(e.toString());
        }
    }

    /**
     * 获取指定菜单路径下的进程列表
     *
     * @param regionName
     * @param path
     * @return
     */
    @RequestMapping(path = "/getWorkerListByPath", method = RequestMethod.GET)
    public Result getWorkerListByPath(String regionName, String path) {
        try {
            List<WorkerInfo> workers = consoleService.getWorkerList(regionName);
            workers = workers.stream().filter(worker -> path.equals(worker.getInfoProps().getString("path")))
                    .collect(Collectors.toList());
            return Result.success(deployWorkers(workers));
        } catch (Exception e) {
            logger.error(e.toString(), e);
            return Result.error(e.toString());
        }
    }

    /**
     * 包装工作进程成层级结构
     *
     * @param workerInfos
     * @return
     */
    private List<DeployWorker> deployWorkers(List<WorkerInfo> workerInfos) {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        List<DeployWorker> deployWorkers = new ArrayList<>();
        Map<String, List<WorkerInfo>> deployGroup = workerInfos.stream()
                .collect(Collectors.groupingBy(WorkerInfo::getDeployName));
        for (Entry<String, List<WorkerInfo>> en1 : deployGroup.entrySet()) {
            DeployWorker deployWorker = new DeployWorker();
            deployWorker.setDeployName(en1.getKey());
            Map<String, List<WorkerInfo>> group = en1.getValue().stream()
                    .collect(Collectors.groupingBy(WorkerInfo::getGroupName));
            for (Entry<String, List<WorkerInfo>> en2 : group.entrySet()) {
                GroupWorker groupWorker = new GroupWorker();
                groupWorker.setGroupName(en2.getKey());
                List<WorkerInfo> workers = en2.getValue().stream().sorted(Comparator.comparingInt(WorkerInfo::getIndex))
                        .collect(Collectors.toList());
                for (WorkerInfo workerInfo : workers) {
                    groupWorker.setGroupDesc(workerInfo.getGroupDesc());
                    deployWorker.setPath(workerInfo.getInfoProps().getString("path", "/"));
                    Worker worker = new Worker();
                    worker.setRegionName(workerInfo.getRegionName());
                    worker.setDeployName(workerInfo.getDeployName());
                    worker.setGroupName(workerInfo.getGroupName());
                    worker.setWorkerName(workerInfo.getWorkerName());
                    worker.setClassName(workerInfo.getClassName());
                    worker.setWorkerDesc(workerInfo.getWorkerDesc());
                    worker.setIndex(workerInfo.getIndex());
                    worker.setOnline(workerInfo.getOnline());
                    if (workerInfo.getStartTime() != 0) {
                        worker.setStartTime(sdf.format(new Date(workerInfo.getStartTime())));
                    }
                    worker.setHost(workerInfo.getHost());
                    worker.setHomePath(workerInfo.getHomePath());
                    worker.setUi(workerInfo.getUi());
                    worker.setPid(workerInfo.getPid());
                    worker.setHeapMemory(workerInfo.getHeapMemory());
                    worker.setNonHeapMemory(workerInfo.getNonHeapMemory());
                    worker.setLiveThreadCount(workerInfo.getLiveThreadCount());
                    groupWorker.getWorkers().add(worker);
                }
                deployWorker.getGroupWorkers().add(groupWorker);
            }
            deployWorkers.add(deployWorker);
        }
        return deployWorkers;
    }

    /**
     * 启动工作进程
     *
     * @param datas
     * @return
     */
    @RequestMapping(path = "/startWorker", method = RequestMethod.POST)
    public Result startWorker(@RequestBody Map<String, String> datas) {
        try {
            String regionName = datas.get("regionName");
            String deployName = datas.get("deployName");
            String groupName = datas.get("groupName");
            String workerName = datas.get("workerName");
            int index = Integer.parseInt(datas.get("index"));
            consoleService.startWorker(regionName, deployName, groupName, workerName, index);
            return Result.success();
        } catch (Exception e) {
            logger.error(e.toString(), e);
            return Result.error(e.toString());
        }
    }

    /**
     * 停止工作进程
     *
     * @param datas
     * @return
     */
    @RequestMapping(path = "/stopWorker", method = RequestMethod.POST)
    public Result stopWorker(@RequestBody Map<String, String> datas) {
        try {
            String regionName = datas.get("regionName");
            String deployName = datas.get("deployName");
            String groupName = datas.get("groupName");
            String workerName = datas.get("workerName");
            int index = Integer.parseInt(datas.get("index"));
            String force = datas.get("force");
            boolean isForce = StringUtils.isEmpty(force) ? false : Boolean.valueOf(force);
            consoleService.stopWorker(regionName, deployName, groupName, workerName, index, isForce);
            return Result.success();
        } catch (Exception e) {
            logger.error(e.toString(), e);
            return Result.error(e.toString());
        }
    }

    /**
     * 获取工作进程的配置列表
     *
     * @param regionName
     * @param deployName
     * @return
     */
    @RequestMapping(path = "/getWorkerConfList", method = RequestMethod.GET)
    public Result getWorkerConfList(String regionName, String deployName) {
        try {
            return Result.success(consoleService.getWorkerConfList(regionName, deployName));
        } catch (Exception e) {
            logger.error(e.toString(), e);
            return Result.error(e.toString());
        }
    }

    /**
     * 获取配置存放的根路径列表
     *
     * @param sys
     * @param regionName
     * @param deployName
     * @return
     */
    @RequestMapping(path = "/getConfRoot", method = RequestMethod.GET)
    public Result getConfRoot(boolean sys, String regionName, String deployName) {
        try {
            if (sys) {
                return Result.success(Lists.newArrayList("/conf"));
            } else {
                return Result.success(consoleService.getWorkerConfRoot(regionName, deployName));
            }
        } catch (Exception e) {
            logger.error(e.toString(), e);
            return Result.error(e.toString());
        }
    }

    /**
     * 获取离线任务列表
     *
     * @param regionName
     * @return
     */
    @RequestMapping(path = "/getJobList", method = RequestMethod.GET)
    public Result getJobList(String regionName) {
        try {
            if (!consoleService.checkConnect()) {
                return Result.error("请先启动代理进程和管理进程");
            }
            return Result.success(deployJobs(consoleService.getJobList(regionName)));
        } catch (Exception e) {
            logger.error(e.toString(), e);
            return Result.error(e.toString());
        }
    }

    /**
     * 获取指定菜单路径下的离线任务列表
     *
     * @param regionName
     * @param path
     * @return
     */
    @RequestMapping(path = "/getJobListByPath", method = RequestMethod.GET)
    public Result getJobListByPath(String regionName, String path) {
        try {
            List<JobInfo> jobs = consoleService.getJobList(regionName);
            jobs = jobs.stream().filter(job -> path.equals(job.getInfoProps().getString("path")))
                    .collect(Collectors.toList());
            return Result.success(deployJobs(jobs));
        } catch (Exception e) {
            logger.error(e.toString(), e);
            return Result.error(e.toString());
        }
    }

    /**
     * 包装离线任务成层级结构
     *
     * @param jobInfos
     * @return
     */
    private List<DeployJob> deployJobs(List<JobInfo> jobInfos) {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        List<DeployJob> deployJobs = new ArrayList<>();
        Map<String, List<JobInfo>> deployGroup = jobInfos.stream()
                .collect(Collectors.groupingBy(JobInfo::getDeployName));
        for (Entry<String, List<JobInfo>> en1 : deployGroup.entrySet()) {
            DeployJob deployJob = new DeployJob();
            deployJob.setDeployName(en1.getKey());
            Map<String, List<JobInfo>> group = en1.getValue().stream()
                    .collect(Collectors.groupingBy(JobInfo::getGroupName));
            for (Entry<String, List<JobInfo>> en2 : group.entrySet()) {
                GroupJob groupJob = new GroupJob();
                groupJob.setGroupName(en2.getKey());
                List<JobInfo> jobs = en2.getValue().stream().sorted(Comparator.comparingInt(JobInfo::getSn))
                        .collect(Collectors.toList());
                for (JobInfo jobInfo : jobs) {
                    groupJob.setGroupDesc(jobInfo.getGroupDesc());
                    groupJob.setCron(jobInfo.getCron());
                    deployJob.setPath(jobInfo.getInfoProps().getString("path", "/"));
                    Job job = new Job();
                    job.setSn(jobInfo.getSn());
                    job.setRegionName(jobInfo.getRegionName());
                    job.setDeployName(jobInfo.getDeployName());
                    job.setGroupName(jobInfo.getGroupName());
                    job.setCron(jobInfo.getCron());
                    job.setGroupDesc(jobInfo.getGroupDesc());
                    job.setJobName(jobInfo.getJobName());
                    job.setClassName(jobInfo.getClassName());
                    job.setJobDesc(jobInfo.getJobDesc());
                    job.setTime(jobInfo.getTime());
                    job.setHost(jobInfo.getHost());
                    job.setRun(jobInfo.isRun());
                    job.setHomePath(jobInfo.getHomePath());
                    if (jobInfo.getUptime() != 0) {
                        job.setStartTime(sdf.format(new Date(jobInfo.getUptime())));
                    }
                    if (jobInfo.getLastRunTime() != 0) {
                        job.setLastRunTime(sdf.format(new Date(jobInfo.getLastRunTime())));
                    }
                    groupJob.getJobs().add(job);
                }
                deployJob.getGroupJobs().add(groupJob);
            }
            deployJobs.add(deployJob);
        }
        return deployJobs;
    }

    /**
     * 启动离线任务调度器
     *
     * @param datas
     * @return
     */
    @RequestMapping(path = "/startJob", method = RequestMethod.POST)
    public Result startJob(@RequestBody Map<String, String> datas) {
        try {
            String regionName = datas.get("regionName");
            String deployName = datas.get("deployName");
            String groupName = datas.get("groupName");
            String jobName = datas.get("jobName");
            consoleService.startJob(regionName, deployName, groupName, jobName);
            return Result.success();
        } catch (Exception e) {
            logger.error(e.toString(), e);
            return Result.error(e.toString());
        }
    }

    /**
     * 停止离线任务调度器
     *
     * @param datas
     * @return
     */
    @RequestMapping(path = "/stopJob", method = RequestMethod.POST)
    public Result stopJob(@RequestBody Map<String, String> datas) {
        try {
            String regionName = datas.get("regionName");
            String deployName = datas.get("deployName");
            String groupName = datas.get("groupName");
            String jobName = datas.get("jobName");
            consoleService.stopJob(regionName, deployName, groupName, jobName);
            return Result.success();
        } catch (Exception e) {
            logger.error(e.toString(), e);
            return Result.error(e.toString());
        }
    }

    /**
     * 运行离线任务
     *
     * @param datas
     * @return
     */
    @RequestMapping(path = "/runJob", method = RequestMethod.POST)
    public Result runJob(@RequestBody Map<String, String> datas) {
        try {
            String regionName = datas.get("regionName");
            String deployName = datas.get("deployName");
            String groupName = datas.get("groupName");
            String jobName = datas.get("jobName");
            long time = Long.parseLong(datas.get("time").toString());
            consoleService.runJob(regionName, deployName, groupName, jobName, time);
            return Result.success();
        } catch (Exception e) {
            logger.error(e.toString(), e);
            return Result.error(e.toString());
        }
    }

    /**
     * 杀死离线任务
     *
     * @param datas
     * @return
     */
    @RequestMapping(path = "/killJob", method = RequestMethod.POST)
    public Result killJob(@RequestBody Map<String, String> datas) {
        try {
            String regionName = datas.get("regionName");
            String deployName = datas.get("deployName");
            String groupName = datas.get("groupName");
            String jobName = datas.get("jobName");
            consoleService.killJob(regionName, deployName, groupName, jobName);
            return Result.success();
        } catch (Exception e) {
            logger.error(e.toString(), e);
            return Result.error(e.toString());
        }
    }

    /**
     * 获取指定路径的日志内容
     *
     * @param host
     * @param path
     * @return
     */
    @RequestMapping(path = "/getLogContent", method = RequestMethod.GET)
    public Result getLogContent(String host, String path) {
        try {
            return Result.success(consoleService.getLogContent(host, path));
        } catch (Exception e) {
            logger.error(e.toString(), e);
            return Result.error(e.toString());
        }
    }

    /**
     * 清空指定路径的日志内容
     *
     * @param datas
     * @return
     */
    @RequestMapping(path = "/clearLogContent", method = RequestMethod.POST)
    public Result clearLogContent(@RequestBody Map<String, String> datas) {
        try {
            String host = datas.get("host");
            String path = datas.get("path");
            consoleService.clearLogContent(host, path);
            return Result.success();
        } catch (Exception e) {
            logger.error(e.toString(), e);
            return Result.error(e.toString());
        }
    }

    /**
     * 下载文件
     *
     * @param host
     * @param path
     * @param res
     * @return
     */
    @RequestMapping(path = "/downLogContent", method = RequestMethod.GET)
    public ResponseEntity<String> downLogContent(String host, String path, HttpServletResponse res) {
        String content = "";
        try {
            File file = consoleService.downLogContent(host, path);
            logger.info("file:" + file.getAbsolutePath());
            if (file != null && file.exists()) {
                String fileName = URLEncoder.encode(FilenameUtils.getName(path), S.charset);
                long length = file.length();
                res.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
                res.setHeader("Content-Disposition", String.format("attachment; filename=\"%s\"", fileName));
                res.setHeader("Pragma", "no-cache");
                res.setHeader("Expires", "0");
                res.setContentType("application/x-download");
                res.setContentLength((int) length);
                InputStream input = new FileInputStream(file);
                BufferedInputStream bis = new BufferedInputStream(input);
                byte[] buff = new byte[10240];
                int len = 0;
                while ((len = bis.read(buff)) > 0) {
                    res.getOutputStream().write(buff, 0, len);
                }
                bis.close();
                res.getOutputStream().close();
                file.delete();
                return null;
            } else {
                content = "not exist for:" + path;
            }
        } catch (Exception e) {
            logger.error(e.toString(), e);
            content = e.toString();
        }
        String fileName = "error";
        long length = content.length();
        HttpHeaders headers = new HttpHeaders();
        headers.add("Cache-Control", "no-cache, no-store, must-revalidate");
        headers.add("Content-Disposition", String.format("attachment; filename=\"%s\"", fileName));
        headers.add("Pragma", "no-cache");
        headers.add("Expires", "0");
        return ResponseEntity.ok().headers(headers).contentLength(length)
                .contentType(MediaType.parseMediaType("application/octet-stream")).body(content);
    }

    /**
     * 获取日志列表
     *
     * @param regionName
     * @param host
     * @param dir
     * @return
     */
    @RequestMapping(path = "/getHostLogList", method = RequestMethod.GET)
    public Result getHostLogList(String regionName, String host, String dir) {
        try {
            List<String> logs = consoleService.getHostLogList(regionName, host, dir).stream()
                    .sorted(Comparator.naturalOrder()).collect(Collectors.toList());
            return Result.success(logs);
        } catch (Exception e) {
            logger.error(e.toString(), e);
            return Result.error(e.toString());
        }
    }

    /**
     * 重新解析worker、job配置文件
     *
     * @param regionName
     * @return
     */
    @RequestMapping(path = "/loadConf", method = RequestMethod.GET)
    public Result loadConf(String regionName) {
        try {
            consoleService.loadConf(regionName);
            return Result.success();
        } catch (Exception e) {
            logger.error(e.toString(), e);
            return Result.error(e.toString());
        }
    }

    /**
     * 获取主机列表
     *
     * @return
     */
    @RequestMapping(path = "/getNodeInfos", method = RequestMethod.GET)
    public Result getNodeInfos() {
        try {
            List<NodeInfo> nodes = consoleService.getNodeInfos().stream()
                    .sorted(Comparator.comparing(NodeInfo::getHost)).collect(Collectors.toList());
            return Result.success(nodes);
        } catch (Exception e) {
            logger.error(e.toString(), e);
            return Result.error(e.toString());
        }
    }

    /**
     * 获取主机的java进程列表
     *
     * @param host
     * @return
     */
    @RequestMapping(path = "/getJvmList", method = RequestMethod.GET)
    public Result getJvmList(String host) {
        try {
            List<JvmInfo> jvmInfos = consoleService.getJvmInfo(host).stream().map(jvmInfo -> {
                jvmInfo.setHost(host);
                return jvmInfo;
            }).sorted(Comparator.comparingInt(JvmInfo::getPid)).collect(Collectors.toList());
            return Result.success(jvmInfos);
        } catch (Exception e) {
            logger.error(e.toString(), e);
            return Result.error(e.toString());
        }
    }

    @RequestMapping(path = "/downCmdJvmInfo", method = RequestMethod.GET)
    public ResponseEntity<String> downCmdJvmInfo(String host, String type, String pid, HttpServletResponse res) {
        try {
            File file = consoleService.downCmdJvmInfo(host, type, pid);
            if (file != null && file.exists()) {
                logger.info("file:" + file.getAbsolutePath());
                String fileName = pid + "-" + type + ".txt";
                if ("jmap".equals(type)) {
                    fileName = pid + "-" + type + ".hprof";
                }
                long length = file.length();
                res.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
                res.setHeader("Content-Disposition", String.format("attachment; filename=\"%s\"", fileName));
                res.setHeader("Pragma", "no-cache");
                res.setHeader("Expires", "0");
                res.setContentType("application/x-download");
                res.setContentLength((int) length);
                InputStream input = new FileInputStream(file);
                BufferedInputStream bis = new BufferedInputStream(input);
                byte[] buff = new byte[10240];
                int len = 0;
                while ((len = bis.read(buff)) > 0) {
                    res.getOutputStream().write(buff, 0, len);
                }
                bis.close();
                res.getOutputStream().close();
                file.delete();
            } else {
                String fileName = "error";
                String content = "not exist for:" + type + " " + pid;
                long length = content.length();
                HttpHeaders headers = new HttpHeaders();
                headers.add("Cache-Control", "no-cache, no-store, must-revalidate");
                headers.add("Content-Disposition", String.format("attachment; filename=\"%s\"", fileName));
                headers.add("Pragma", "no-cache");
                headers.add("Expires", "0");
                return ResponseEntity.ok().headers(headers).contentLength(length)
                        .contentType(MediaType.parseMediaType("application/octet-stream")).body(content);
            }
        } catch (Exception e) {
            logger.error(e.toString(), e);
        }
        return null;
    }

    @RequestMapping(path = "/fileTreeNode", method = RequestMethod.GET)
    public Result fileTreeNode(String host, String path) {
        try {
            return Result.success(consoleService.fileTreeNode(host, path));
        } catch (Exception e) {
            logger.error(e.toString(), e);
            return Result.error(e.toString());
        }
    }

    @RequestMapping(path = "/fileInfoList", method = RequestMethod.GET)
    public Result fileInfoList(String host, String path) {
        try {
            return Result.success(consoleService.fileInfoList(host, path));
        } catch (Exception e) {
            logger.error(e.toString(), e);
            return Result.error(e.toString());
        }
    }

    @RequestMapping(path = "/uploadFile", method = RequestMethod.POST)
    public Result uploadFile(String host, String path, @RequestParam("file") MultipartFile file) {
        try {
            String fileName = file.getOriginalFilename();
            logger.info("uploadFile fileName:" + fileName);
            File tmpFile = new File(HomePath.getTmpFilePath(), UUID.randomUUID().toString());
            if (!tmpFile.getParentFile().exists()) {
                tmpFile.getParentFile().mkdirs();
            }
            file.transferTo(tmpFile);
            consoleService.uploadFile(host, path, fileName, tmpFile);
            return Result.success();
        } catch (Exception e) {
            logger.error(e.toString(), e);
            return Result.error(e.toString());
        }
    }

    @RequestMapping(path = "/deleteFile", method = RequestMethod.POST)
    public Result deleteFile(@RequestBody Map<String, String> datas) {
        try {
            String host = datas.get("host");
            String path = datas.get("path");
            consoleService.deleteFile(host, path);
            return Result.success();
        } catch (Exception e) {
            logger.error(e.toString(), e);
            return Result.error(e.toString());
        }
    }

    @RequestMapping(path = "/getAllSystemInfo", method = RequestMethod.GET)
    public Result getAllSystemInfo() {
        try {
            if (!consoleService.checkConnect()) {
                return Result.error("请先启动代理进程和管理进程");
            }
            return Result.success(consoleService.getAllSystemInfo());
        } catch (Exception e) {
            logger.error(e.toString(), e);
            return Result.error(e.toString());
        }
    }

    @RequestMapping(path = "/removeAlarmInfo", method = RequestMethod.GET)
    public Result removeAlarmInfo(String type) {
        try {
            consoleService.removeAlarmInfo(type);
            return Result.success();
        } catch (Exception e) {
            logger.error(e.toString(), e);
            return Result.error(e.toString());
        }
    }

    @RequestMapping(path = "/getAllAlarmInfo", method = RequestMethod.GET)
    public Result getAllAlarmInfo() {
        try {
            if (!consoleService.checkConnect()) {
                return Result.error("请先启动代理进程和管理进程");
            }
            return Result.success(consoleService.getAllAlarmInfo());
        } catch (Exception e) {
            logger.error(e.toString(), e);
            return Result.error(e.toString());
        }
    }

    @RequestMapping(path = "/getPackageInfo", method = RequestMethod.GET)
    public Result getPackageInfo() {
        try {
            if (!consoleService.checkConnect()) {
                return Result.error("请先启动代理进程和管理进程");
            }
            return Result.success(consoleService.getPackageInfo());
        } catch (Exception e) {
            logger.error(e.toString(), e);
            return Result.error(e.toString());
        }
    }

    @RequestMapping(path = "/getDeployInfo", method = RequestMethod.GET)
    public Result getDeployInfo(String regionName) {
        try {
            return Result.success(consoleService.getDeployInfo(regionName));
        } catch (Exception e) {
            logger.error(e.toString(), e);
            return Result.error(e.toString());
        }
    }

    @RequestMapping(path = "/createDeployDir", method = RequestMethod.GET)
    public Result createDeployDir(String regionName, String deployName) {
        try {
            consoleService.createDeployDir(regionName, deployName);
            return Result.success();
        } catch (Exception e) {
            logger.error(e.toString(), e);
            return Result.error(e.toString());
        }
    }

    @RequestMapping(path = "/getDeployVersionList", method = RequestMethod.GET)
    public Result getDeployVersionList(String regionName, String deployName) {
        try {
            return Result.success(consoleService.getDeployVersionList(regionName, deployName));
        } catch (Exception e) {
            logger.error(e.toString(), e);
            return Result.error(e.toString());
        }
    }

    @RequestMapping(path = "/changeDeployProp", method = RequestMethod.GET)
    public Result changeDeployProp(String regionName, String deployName, String key, String value) {
        try {
            consoleService.changeDeployProp(regionName, deployName, key, value);
            return Result.success();
        } catch (Exception e) {
            logger.error(e.toString(), e);
            return Result.error(e.toString());
        }
    }

    @RequestMapping(path = "/getGatewayProxyInfo", method = RequestMethod.GET)
    public Result getGatewayProxyInfo(String gatewayGroupName, String gatewayWorkerName, int gatewayIndex,
                                      String gatewayRegionName, String gatewayDeployName) {
        try {
            return Result.success(consoleService.getGatewayProxyInfo(gatewayGroupName, gatewayWorkerName, gatewayIndex,
                    gatewayRegionName, gatewayDeployName));
        } catch (Exception e) {
            logger.error(e.toString(), e);
            return Result.error(e.toString());
        }
    }

    @RequestMapping(path = "/downFile", method = RequestMethod.GET)
    public ResponseEntity<String> downFile(String path, HttpServletResponse res) {
        try {
            path = URLDecoder.decode(path, "UTF-8");
            File file = new File(HomePath.getDataPath(), path);
            if (file != null && file.exists()) {
                logger.info("file:" + file.getAbsolutePath());
                String fileName = URLEncoder.encode(file.getName(), S.charset);
                long length = file.length();
                res.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
                res.setHeader("Content-Disposition", String.format("attachment; filename=\"%s\"", fileName));
                res.setHeader("Pragma", "no-cache");
                res.setHeader("Expires", "0");
                res.setContentType("application/x-download");
                res.setContentLength((int) length);
                InputStream input = new FileInputStream(file);
                BufferedInputStream bis = new BufferedInputStream(input);
                byte[] buff = new byte[10240];
                int len = 0;
                while ((len = bis.read(buff)) > 0) {
                    res.getOutputStream().write(buff, 0, len);
                }
                bis.close();
                res.getOutputStream().close();
            } else {
                String fileName = "error";
                String content = "not exist for:" + path;
                long length = content.length();
                HttpHeaders headers = new HttpHeaders();
                headers.add("Cache-Control", "no-cache, no-store, must-revalidate");
                headers.add("Content-Disposition", String.format("attachment; filename=\"%s\"", fileName));
                headers.add("Pragma", "no-cache");
                headers.add("Expires", "0");
                return ResponseEntity.ok().headers(headers).contentLength(length)
                        .contentType(MediaType.parseMediaType("application/octet-stream")).body(content);
            }
        } catch (Exception e) {
            logger.error(e.toString(), e);
        }
        return null;
    }

    @RequestMapping(path = "/getContent", method = RequestMethod.GET)
    public Result getContent(String path) {
        InputStream input = null;
        try {
            path = URLDecoder.decode(path, "UTF-8");
            File file = new File(HomePath.getDataPath(), path);
            if (file.exists()) {
                input = new FileInputStream(file);
                return Result.success(IOUtils.toString(input, S.charset));
            }
        } catch (Exception e) {
            logger.error(e.toString(), e);
            return Result.error(e.toString());
        } finally {
            IOUtils.closeQuietly(input);
        }
        return Result.error("");
    }
}
